package hamaster.jpbc.util;

import hamaster.jpbc.gui.ProgressListener;
import hamaster.jpbc.pbc.PBCDriver;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;

/**
 * 提供一些Hash算法的工具类
 */
public final class Hash {

	private Hash() {}

	/**
	 * 计算字符串的MD5摘要
	 * @param str 要计算的字符串
	 * @return MD5摘要
	 */
	public static byte[] md5(String str) {
		return digest(str, "MD5");
	}

	/**
	 * 计算字符串的SHA-1摘要
	 * @param str 要计算的字符串
	 * @return SHA-1摘要
	 */
	public static byte[] sha1(String str) {
		return digest(str, "SHA-1");
	}

	/**
	 * 计算字符串的SHA-256摘要
	 * @param str 要计算的字符串
	 * @return SHA-256摘要
	 */
	public static byte[] sha256(String str) {
		return digest(str, "SHA-256");
	}

	/**
	 * 计算文件的MD5摘要
	 * @param file 要计算的文件
	 * @return 文件的MD5摘要
	 * @throws IOException IO异常 如文件不存在
	 */
	public static byte[] md5(File file) throws IOException {
		return digest(new FileInputStream(file), file.length(), null, "MD5");
	}
	
	/**
	 * 计算文件的SHA-1摘要
	 * @param file 要计算的文件
	 * @return 文件的SHA-1摘要
	 * @throws IOException IO异常 如文件不存在
	 */
	public static byte[] sha1(File file) throws IOException {
		return digest(new FileInputStream(file), file.length(), null, "SHA-1");
	}
	
	/**
	 * 计算文件的SHA-256摘要
	 * @param file 要计算的文件
	 * @return 文件的SHA-256摘要
	 * @throws IOException IO异常 如文件不存在
	 */
	public static byte[] sha256(File file) throws IOException {
		return digest(new FileInputStream(file), file.length(), null, "SHA-256");
	}

	/**
	 * 计算文件的MD5摘要
	 * @param file 要计算的文件
	 * @param progressListener 进度监听器
	 * @return 文件的MD5摘要
	 * @throws IOException IO异常 如文件不存在
	 */
	public static byte[] md5(File file, ProgressListener progressListener) throws IOException {
		return digest(new FileInputStream(file), file.length(), progressListener, "MD5");
	}

	/**
	 * 计算文件的SHA-1摘要
	 * @param file 要计算的文件
	 * @param progressListener 进度监听器
	 * @return 文件的SHA-1摘要
	 * @throws IOException IO异常 如文件不存在
	 */
	public static byte[] sha1(File file, ProgressListener progressListener) throws IOException {
		InputStream in = new FileInputStream(file);
		byte[] res = digest(in, file.length(), progressListener, "SHA-1");
		in.close();
		return res;
	}

	/**
	 * 计算文件的SHA-256摘要
	 * @param file 要计算的文件
	 * @param progressListener 进度监听器
	 * @return 文件的SHA-256摘要
	 * @throws IOException IO异常 如文件不存在
	 */
	public static byte[] sha256(File file, ProgressListener progressListener) throws IOException {
		return digest(new FileInputStream(file), file.length(), progressListener, "SHA-256");
	}

	/**
	 * 将一个Properties按照键的自然顺序格式化为字符串
	 * @param props 要格式化的Properties
	 * @return 格式化后的字符串
	 */
	public static String propertiesToHexString(Properties props) {
		Map<String, String> p = new TreeMap<String, String>();
		Set<String> ks = props.stringPropertyNames();
		for (String k : ks) {
			p.put(k, props.getProperty(k));
		}
		StringBuffer sb = new StringBuffer();
		for (String key : p.keySet()) {
			sb.append(key);
			sb.append("=");
			sb.append(p.get(key));
			sb.append("\r\n");
		}
		return PBCDriver.hex(sb.toString().getBytes());
	}

	private static byte[] digest(String str, String algorithm) {
		try {
			byte[] bt = str.getBytes();
			MessageDigest md = MessageDigest.getInstance(algorithm);
			md.update(bt);
			return md.digest();
		} catch (NoSuchAlgorithmException e) {
			return null;
		}
	}

	private static byte[] digest(InputStream in, long length, ProgressListener progressListener, String algorithm) throws IOException {
		try {
			MessageDigest md = MessageDigest.getInstance(algorithm);
			byte[] buffer = new byte[1024];
			int l;
			long total = 0;
			while ((l = in.read(buffer)) > 0) {
				md.update(buffer, 0, l);
				if (progressListener != null) {
					total += l;
					progressListener.progressUpdated((int) (total * 100 / length));
				}
			}
			byte[] data = md.digest();
			if (progressListener != null)
				progressListener.progressUpdated(100);
			return data;
		} catch (NoSuchAlgorithmException e) {
			return null;
		}
	}
}
